Game Development Path

When worlds collide - what is collision detection

Worlds colliding -small

Collision detection is one of the most important features in the game. Without it, total chaos would ensue! The character would be able to walk through walls, fall through floors and it would just be unplayable.

Or perhaps it would be fun? Why don’t we try to disable collision detection to see what happens first.

In Boy.java, find the moveLeft and moveRight functions. In them, you will find these lines respectively:

// Attempt to move left by DISPLACEMENT amount
currentX = checkMove(currentX, currentX - DISPLACEMENT, isLastLevel);
// Attempt to move right by DISPLACEMENT amount
currentX = checkMove(currentX, currentX + DISPLACEMENT, isLastLevel);

Try taking out the element of checking whether the move is valid, by replacing them with:

currentX -= DISPLACEMENT;

and

currentX += DISPLACEMENT;

Try running the game now. What happens?

Character without walking collision

Okay, now you can walk through blocks. It’s a god-like power, not bad, huh ᕙ( ͡° ͜ʖ ͡°)ᕗ

You can now also walk beyond the edge of the map. That’s less great… ʕ ͡° ʖ̯ ͡°ʔ

Let’s undo those changes for now.

So, what is collision detection? It is being able to tell when two objects collide - such as the character and a block. If a collision is detected, we can act accordingly and stop the character from walking into blocks, for example.

Hitting your head against a wall - how do we detect collisions

In high level overview, each object in the game has a bounding box. The character has one and so do all the blocks. A bounding box is just a rectangle around an object, that approximates its shape.

We can visualise the character’s bounding box by drawing it in the game! If you open up PlayPanel.java, in the paintComponent function, you will find a line that is commented out:

//g2.draw(boy.getBoundingBox());

Uncomment this line and run the game now. You should be able to see a blue rectangle around the character.

Character bounding box

As you can see, the character’s hand pokes out of this bounding box, so it is an imperfect approximation. It could be made bigger to fit the whole character, but then that would also include quite a lot of empty space, so there’s some art to choosing the right size.

When we have two bounding boxes, we can tell whether they intersect by doing some basic maths to check whether the rectangles overlap.

Check out those moves - how does checkMove work

It’s finally time to figure out, how does this magic checkMove function that prevents walking through blocks and other naughty behaviour work?

This function in Boy.java should look like this:

// Check whether the location the player wants to move into
// Is not out of bounds and does not contain a block
// If so, return the new position
// Otherwise, return the old one
private int checkMove(int oldX, int newX, boolean isLastLevel) {
    if (newX <= 0) {
        return 0;
    }

    if (newX >= (Settings.WINDOW_WIDTH - BOY_WIDTH) && isLastLevel) {
        return (Settings.WINDOW_WIDTH - BOY_WIDTH);
    }

    boundingBox.setLocation(newX, currentY);

    // Get the tile position (in the tiled map)
    // Relative to the tile in front of the character
    int footCol;

    if (facingDirection == KeyEvent.VK_RIGHT) {
        int footX = (int) boundingBox.getMinX();
        footCol = (footX / Settings.TILE_SIZE) + 1;
    } else {
        int footX = (int) boundingBox.getMaxX();
        footCol = (footX / Settings.TILE_SIZE) - 1;
    }

    // The character is at the edge of the map and the tile in front of it
    // Would be out of bounds, so skip checking it
    if (footCol < 0 || footCol >= World.cols) {
        return newX;
    }

    int footY = (int) (boundingBox.getMaxY());
    int footRow = ((footY-1) / Settings.TILE_SIZE);

    Block tileInFrontOfFoot = World.map[footRow][footCol];

    if (!tileInFrontOfFoot.empty()
        && tileInFrontOfFoot.intersects(boundingBox)) {
        return oldX;
    }

    return newX;
}

Whenever moveLeft or moveRight are called, they in turn call checkMove with the arguments oldX as the current position of the character, newX as the position the character would like to move to and isLastLevel as a boolean that simply tells whether this level is the last one.

First, checkMove checks that newX is positive. If not, it returns 0 as the new X position for the character. This prevents moving past the left edge of the screen.

Next, if the current level is the last one, it also prevents moving past the right edge of the screen.

Provided these two checks were passed, it updates the bounding box to be at the new desired position. But that’s not all, there’s more!

By getting the left or the right edge of the bounding box, depending on which way the character is facing, we can tell the position of the block in front of the character.

It talks about the position in terms of rows and columns. How does this work?

Without going into too much detail, as this is explained in the game design pathway, the game maps are grids of blocks. You can uniquely identify any particular block with its row and column position in the map.

As each block is the same fixed size, we can divide a particular pixel coordinate position on the game screen by this fixed tile size to get the column or row of that position.

Having found the position of the block in front of the character, we check if this position is out of the map bounds. If so, it is safe to walk forward (because going beyond the screen edge was already covered earlier) and the new position is returned.

Otherwise, there is a block in front of the character within the map bounds. If that block is not empty, and moving to the new position would cause the character to intersect with it, then that would mean walking into the block. In this case, we return the old position (and the bounding box is reset right after, in the moveLeft or moveRight function).

Finally, if all the checks have passed, we can return the new position with peace of mind.

Yoga sunset for peace of mind

Are we there yet? - it seems we are

By now you should have a good idea of how collision detection works in the game.

We have only looked through what exactly goes on when moving left or right, but similar principles apply to vertical movement too. For an exercise, see if you can figure out the handleFalling and checkBlockCollisions functions in Boy.java.

They are the functions responsible for ensuring that you can jump and land without falling through blocks:

Jumping with bounding box

If you are asking yourself - what was this all for? Well, a major limitation in the game is that there are only 2 kinds of elements in the map - air that the character can walk through and blocks that the character can’t. But who says you couldn’t implement a kind of block, such as water, that is actually fine to walk into (つ ♥ ͜ʖ ♥)つ

As usual, here is your portal to the next guide. See you on the other side!

Portal to the next guide